"""Parses the PropertyValueAliases.txt and UnicodeBlocks.txt files and defines
four dictionaries: "category", "bidi", "block", and "entity".
"""
import re
import textwrap
import unittest

import util

_gen_cat = {}
_bidi_cat = {}
_block = {}
_entity = {}

def getGeneralCategory(code):
  return _gen_cat.get(code).title()

def getBidiCategory(code):
  return _bidi_cat.get(code).title()

def getBlockName(code):
  # already formatted, so don't call .title()
  return _block.get(code)

def getEntityName(code):
  return _entity.get(code)

category_re = re.compile(r"""
  # Examples:
  #   gc ; C         ; Other                            # Cc | Cf | Cn | Co | Cs
  #   gc ; Cc        ; Control                          ; cntrl
  #   gc ; Cf        ; Format
  #   bc ; AL        ; Arabic_Letter
  #   bc ; AN        ; Arabic_Number
  #   bc ; B         ; Paragraph_Separator
  
  ^ \s*          # optional whitespace at the beginning
  
  (?P<type>      # start of type group 
    gc | bc      # gc for general category, bc for bidi category
  ) \s* ; \s*    # end of group and semicolon (optional whitespace)
  
  (?P<code>      # start of code group
    [^\s;]+      # category code, no whitespace or semicolon (like C, Cc, or Cf)
  ) \s* ; \s*    # end of group and semicolon (optional whitespace)
  
  (?P<name>      # start of name group
    [^\s;]+      # name, no whitespace or semicolon (like Other, Control, or Format)
  )              # end of group
  """, re.VERBOSE)

block_re = re.compile(r"""
  # Examples:
  #   0000..007F; Basic Latin
  #   0080..00FF; Latin-1 Supplement
  #   0100..017F; Latin Extended-A
  #   0180..024F; Latin Extended-B
  #   0250..02AF; IPA Extensions
  
  ^ \s*            # start of line (optional whitespace)
  
  (?P<startcode>   # start of startcode group
    [0-9a-fA-F]+   # hexadecimal characters
  ) \.\.           # end group and two periods
  
  (?P<endcode>     # start of startcode group
    [0-9a-fA-F]+   # hexadecimal characters
  ) \s* ; \s*      # end group and semicolon (optional whitespace)
  
  (?P<name>        # start of name group
    [^\r\n]+       # name
  )                # end group
  """, re.VERBOSE)

entity_re = re.compile(r"""
  # Examples:
  #   <!ENTITY ouml    "&#246;">
  #   <!ENTITY dagger  "&#8224;"> <!-- dagger, U+2020 ISOpub -->
  #   <!ENTITY loz     "&#9674;"> <!-- lozenge, U+25CA ISOpub -->
  
  ^ \s*          # optional whitespace at the beginning
  
  <!ENTITY \s+   # <!ENTITY followed by whitespace
    
    (?P<name>    # start of entity name group
      [^\s>]+    # entity name, no whitespace or closing angle bracket
    )            # end group
    
    \s+          # mandatory whitespace
    
    "&\#         # start of decimal entity (full entity looks like "&#2660;")
      (?P<code>  # start of decimal code group
        \d+      # the decimal code point
      )          # end group
    ;"           # end of decimal entity
    
    \s* >        # optional whitespace and the closing bracket
  """, re.VERBOSE)

def parseAliases():
  gen_cat = {}
  bidi_cat = {}
  for line in util.opendatafile("PropertyValueAliases.txt"):
    match = category_re.match(line)
    if match:
      if match.group("type") == "gc":
        d = gen_cat
      if match.group("type") == "bc":
        d = bidi_cat
      code = match.group("code")
      name = match.group("name").replace("_", " ").upper()
      d[code] = name
  return (gen_cat, bidi_cat)

def parseBlocks():
  block = {}
  for line in util.opendatafile("Blocks.txt"):
    match = block_re.match(line)
    if match:
      start = int(match.group("startcode"), 16)
      end = int(match.group("endcode"), 16)
      name = match.group("name")
      for i in range(start, end + 1):
        block[i] = name
  return block

def parseEntities():
  entity = {}
  for entity_file in ("xhtml-lat1.ent", "xhtml-special.ent", "xhtml-symbol.ent"):
    for line in util.opendatafile(entity_file):
      # unencode ampersands
      line = line.replace("&#38;", "&")
      match = entity_re.match(line)
      if match:
        name = match.group("name")
        code = int(match.group("code"))
        value = "&%s;" % name
        entity[code] = value
  return entity

def initData():
  clearData()
  (gen_cat, bidi_cat) = parseAliases()
  _gen_cat.update(gen_cat)
  _bidi_cat.update(bidi_cat)
  _block.update(parseBlocks())
  _entity.update(parseEntities())

def clearData():
  _gen_cat.clear()
  _bidi_cat.clear()
  _block.clear()
  _entity.clear()

class CharDataTestCase(unittest.TestCase):
  def testInitData(self):
    initData()
    self.assertEqual(getGeneralCategory("Sm"), "Math Symbol")
    self.assertEqual(getBidiCategory("WS"), "White Space")
    self.assertEqual(getBlockName(5050), "Cherokee")
    self.assertEqual(getEntityName(38), "&amp;")
  
  def testEntityReGroups(self):
    entity = """<!ENTITY loz      "&#9674;"> <!-- lozenge, U+25CA ISOpub -->"""
    match = entity_re.match(entity)
    self.assertEqual(match.group("name"), "loz")
    self.assertEqual(match.group("code"), "9674")
  
  def testCategoryReGroups(self):
    line = """gc ; Cn        ; Unassigned"""
    match = category_re.match(line)
    self.assertEqual(match.group("type"), "gc")
    self.assertEqual(match.group("code"), "Cn")
    self.assertEqual(match.group("name"), "Unassigned")
    line = """gc ; Co        ; Private_Use"""
    match = category_re.match(line)
    self.assertEqual(match.group("type"), "gc")
    self.assertEqual(match.group("code"), "Co")
    self.assertEqual(match.group("name"), "Private_Use")
    line = """bc ; B         ; Paragraph_Separator"""
    match = category_re.match(line)
    self.assertEqual(match.group("type"), "bc")
    self.assertEqual(match.group("code"), "B")
    self.assertEqual(match.group("name"), "Paragraph_Separator")
    line = """bc ; BN        ; Boundary_Neutral"""
    match = category_re.match(line)
    self.assertEqual(match.group("type"), "bc")
    self.assertEqual(match.group("code"), "BN")
    self.assertEqual(match.group("name"), "Boundary_Neutral")
    line = """bc ; CS        ; Common_Separator"""
    match = category_re.match(line)
    self.assertEqual(match.group("type"), "bc")
    self.assertEqual(match.group("code"), "CS")
    self.assertEqual(match.group("name"), "Common_Separator")
  
  def testBlockReGroups(self):
    line = """0000..007F; Basic Latin"""
    match = block_re.match(line)
    self.assertEqual(match.group("startcode"), "0000")
    self.assertEqual(match.group("endcode"), "007F")
    self.assertEqual(match.group("name"), "Basic Latin")
    line = """02B0..02FF; Spacing Modifier Letters"""
    match = block_re.match(line)
    self.assertEqual(match.group("startcode"), "02B0")
    self.assertEqual(match.group("endcode"), "02FF")
    self.assertEqual(match.group("name"), "Spacing Modifier Letters")
    line = """0300..036F; Combining Diacritical Marks"""
    match = block_re.match(line)
    self.assertEqual(match.group("startcode"), "0300")
    self.assertEqual(match.group("endcode"), "036F")
    self.assertEqual(match.group("name"), "Combining Diacritical Marks")
  
  def testEntityReMatch(self):
    sample_entities = textwrap.dedent("""
      <!ENTITY dagger  "&#8224;"> <!-- dagger, U+2020 ISOpub -->
      <!ENTITY pound  "&#163;"> <!-- pound sign, U+00A3 ISOnum -->
      <!ENTITY curren "&#164;"> <!-- currency sign, U+00A4 ISOnum -->
      """).strip()
    for line in sample_entities.split("\n"):
      if not entity_re.match(line):
        self.fail("entity_re failed to match: %s" % line)
  
  def testCategoryReMatch(self):
    sample_file = textwrap.dedent("""
        gc ; C         ; Other                            # Cc | Cf | Cn | Co | Cs
        gc ; Cc        ; Control                          ; cntrl
        gc ; Cf        ; Format
        gc ; Cn        ; Unassigned
        gc ; Co        ; Private_Use
        gc ; Cs        ; Surrogate
        bc ; AL        ; Arabic_Letter
        bc ; AN        ; Arabic_Number
        bc ; B         ; Paragraph_Separator
        bc ; BN        ; Boundary_Neutral
        bc ; CS        ; Common_Separator
        bc ; EN        ; European_Number
      """).strip()
    for line in sample_file.split("\n"):
      match = category_re.match(line)
      if not match:
        self.fail("category_re failed to match: %s" % line)
  
  def testBlockReMatch(self):
    sample_file = textwrap.dedent("""
      0000..007F; Basic Latin
      0080..00FF; Latin-1 Supplement
      0100..017F; Latin Extended-A
      0180..024F; Latin Extended-B
      0250..02AF; IPA Extensions
      02B0..02FF; Spacing Modifier Letters
      0300..036F; Combining Diacritical Marks
      0370..03FF; Greek and Coptic
      0400..04FF; Cyrillic
      0500..052F; Cyrillic Supplement
      0530..058F; Armenian
      0590..05FF; Hebrew
      """).strip()
    for line in sample_file.split("\n"):
      match = block_re.match(line)
      if not match:
        self.fail("block_re failed to match: %s" % line)
  
  def testParseAliases(self):
    (category_test, bidi_test) = parseAliases()
    self.assertEqual(category_test["Pc"], "CONNECTOR PUNCTUATION")
    self.assertEqual(category_test["Nd"], "DECIMAL NUMBER")
    self.assertEqual(category_test["Sm"], "MATH SYMBOL")
    self.assertEqual(category_test["Co"], "PRIVATE USE")
    self.assertEqual(category_test["Zs"], "SPACE SEPARATOR")
    self.assertEqual(bidi_test["B"], "PARAGRAPH SEPARATOR")
    self.assertEqual(bidi_test["L"], "LEFT TO RIGHT")
    self.assertEqual(bidi_test["LRO"], "LEFT TO RIGHT OVERRIDE")
    self.assertEqual(bidi_test["WS"], "WHITE SPACE")
    self.assertEqual(bidi_test["ES"], "EUROPEAN SEPARATOR")
  
  def testParseBlocks(self):
    block_test = parseBlocks()
    self.assertEqual(block_test[10], "Basic Latin")
    self.assertEqual(block_test[1000], "Greek and Coptic")
    self.assertEqual(block_test[1025], "Cyrillic")
    self.assertEqual(block_test[1500], "Hebrew")
    self.assertEqual(block_test[5050], "Cherokee")
    self.assertEqual(block_test[3600], "Thai")
    self.assertEqual(block_test[8710], "Mathematical Operators")
    self.assertEqual(block_test[67850], "Phoenician")
  
  def testParseEntities(self):
    entities_test = parseEntities()
    self.assertEqual(entities_test[163], "&pound;")
    self.assertEqual(entities_test[169], "&copy;")
    self.assertEqual(entities_test[193], "&Aacute;")
    self.assertEqual(entities_test[34], "&quot;")
    self.assertEqual(entities_test[38], "&amp;")
    self.assertEqual(entities_test[8250], "&rsaquo;")
    self.assertEqual(entities_test[8364], "&euro;")
    self.assertEqual(entities_test[960], "&pi;")
    self.assertEqual(entities_test[8230], "&hellip;")
    self.assertEqual(entities_test[8721], "&sum;")
    self.assertEqual(entities_test[9674], "&loz;")
    self.assertEqual(entities_test[9830], "&diams;")
